package com.example.springgobang.game;

import com.example.springgobang.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;

// 这个类表示 "匹配器", 通过这个类负责完成整个匹配功能
@Component
public class Matcher {
    // 创建三个匹配队列
    // 普通水平玩家
    private Queue<User> normalQueue = new LinkedList<>();
    // 高水平玩家
    private Queue<User> highQueue = new LinkedList<>();
    // 超高水平玩家
    private Queue<User> veryHighQueue = new LinkedList<>();
    // 注入 OnlineUserManager 来获取玩家的状态信息
    @Autowired
    private OnlineUserManager onlineUserManager;
    // 创建 ObjectMapper 用于处理 JSON 数据
    // 这里主要用于对给客户端返回匹配结果的响应进行转换
    private ObjectMapper objectMapper = new ObjectMapper();
    // 注入 RoomManager 来把匹配成功的两玩家放入同一个房间中
    // 并保存好其中的映射关系
    @Autowired
    private RoomManager roomManager;

    // 操作匹配队列的方法:
    // 把玩家放到匹配队列中
    public void add(User user) {
        // 根据玩家的天梯积分来进行划分
        if (user.getScore() < 2000) {
            synchronized (normalQueue) {
                normalQueue.offer(user);
                normalQueue.notify();
            }
            System.out.println("把玩家 " + user.getUsername() + " 加入到了 normalQueue 中!");
        } else if (user.getScore() >= 2000 && user.getScore() < 3000) {
            synchronized (highQueue) {
                highQueue.offer(user);
                highQueue.notify();
            }
            System.out.println("把玩家 " + user.getUsername() + " 加入到了 highQueue 中!");
        } else {
            synchronized (veryHighQueue) {
                veryHighQueue.offer(user);
                veryHighQueue.notify();
            }
            System.out.println("把玩家 " + user.getUsername() + " 加入到了 veryHighQueue 中!");
        }
    }

    // 当玩家点击停止匹配的时候, 就需要把玩家从匹配队列中删除
    public void remove(User user) {
        // 找到玩家对应的段位,进行移出匹配队列
        if (user.getScore() < 2000 && normalQueue.contains(user)) {
            synchronized (normalQueue) {
                normalQueue.remove(user);
            }
            System.out.println("把玩家 " + user.getUsername() + "从 normalQueue 中移除!");
        } else if (user.getScore() >= 2000 && user.getScore() < 3000 && highQueue.contains(user)) {
            synchronized (highQueue) {
                highQueue.remove(user);
            }
            System.out.println("把玩家 " + user.getUsername() + " 从 highQueue 中移除!");
        } else if (veryHighQueue.contains(user)) {
            synchronized (veryHighQueue) {
                veryHighQueue.remove(user);
            }
            System.out.println("把玩家 " + user.getUsername() + " 从 veryHighQueue 中移除!");
        }
    }

    public Matcher() {
        // 创建三个线程, 分别对这三个匹配队列进行操作
        Thread normalThread = new Thread(()->{
            // 扫描 normalQueue
            while (true) {
                handleMatch(normalQueue);
            }
        });
        normalThread.start();
        Thread highThread = new Thread(()->{
            // 扫描 highQueue
            while (true) {
                handleMatch(highQueue);
            }
        });
        highThread.start();
        Thread veryHighThread = new Thread(()->{
            // 扫描 veryHighQueue
            while (true) {
                handleMatch(veryHighQueue);
            }
        });
        veryHighThread.start();
    }

    // 处理匹配的相关逻辑
    private void handleMatch(Queue<User> matchQueue) {
        // 此时加锁的锁对象, 传进来的是 normalQueue 就是对 normalQueue进行加锁, 是 highQueue 就是对 highQueue 进行加锁
        synchronized (matchQueue) {
            try {
                // 五子棋对战至少需要两位玩家
                // 1. 检测匹配队列中的玩家个数是否达到 2
                //    队列的初始情况可能是 空
                //    如果往队列中添加一个元素, 这个时候, 仍然是不能进行后续匹配操作的
                //    因此在这里使用 while 循环检查是更合理的
                while (matchQueue.size() < 2) {
                    // 队列的玩家数量不足两人, 就直接返回
                    matchQueue.wait();
                }
                // 2. 尝试从队列中取出两个玩家
                User player1 = matchQueue.poll();
                User player2 = matchQueue.poll();
                System.out.println("匹配出两个玩家: " + player1.getUsername() + " , " + player2.getUsername());
                // 3. 获取玩家的 WebSocket 的会话
                //    获取会话的目的是为了告诉玩家, 你匹配成功了
                WebSocketSession session1 = onlineUserManager.getFromGameHall(player1.getUserId());
                WebSocketSession session2 = onlineUserManager.getFromGameHall(player2.getUserId());
                // 理论上来说, 我们这里匹配队列中的玩家一定是在线的状态,
                // 这是因为前面的逻辑中已经进行了处理, 当玩家断开连接的时候就把玩家从匹配队列中移除了.
                // 但是我们这里还是要做一次判定, 可以起到一个双重保险的作用
                if (session1 == null) {
                    // 如果玩家1 现在不在线了, 就把玩家2 重新放回匹配队列中
                    matchQueue.offer(player2);
                    return;
                }
                if (session2 == null) {
                    // 如果玩家2 现在下线了, 就把玩家1 重新放回匹配队列中
                    matchQueue.offer(player1);
                    return;
                }
                // 当前能否排到两个玩家是同一个用户的情况吗? 一个玩家入队列两次?
                // 理论上来说这是不会存在的, 这是因为前面的逻辑已经进行了处理:
                // 1) 如果玩家下线, 就会对玩家进行移出匹配队列的操作
                // 2) 并且禁止玩家多开.
                // 但是这里仍然再做一次判定, 以免前面的逻辑出现 bug 时带来的严重后果
                if (session1 == session2) {
                    // 把其中一个玩家放回匹配队列
                    matchQueue.offer(player1);
                    return;
                }
                // 4. 把这两个玩家放到一个游戏房间中
                Room room = new Room();
                roomManager.add(room, player1.getUserId(), player2.getUserId());
                // 5. 给玩家反馈信息: 你匹配到对手了
                //    通过 WebSocket 返回一个 message 为 'matchSuccess' 这样的响应
                //    此处是要给两个玩家都返回 "匹配成功" 这样的信息
                //    因此就需要返回两次
                MatchResponse response1 = new MatchResponse();
                response1.setOk(true);
                response1.setMessage("matchSuccess");
                String json1 = objectMapper.writeValueAsString(response1);
                session1.sendMessage(new TextMessage(json1));

                MatchResponse response2 = new MatchResponse();
                response2.setOk(true);
                response2.setMessage("matchSuccess");
                String json2 = objectMapper.writeValueAsString(response2);
                session2.sendMessage(new TextMessage(json2));
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
